﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using FluentNHibernate.Utils.Reflection;
using NHibernate.Criterion;
using SinBiota.Model.Entities;
using SinBiota.Model.Exceptions;
using SinBiota.Model.Persistence.Repositories.Interfaces;
using NHibernate;

namespace SinBiota.Model.Persistence.Repositories.Implementation
{
    public class RepositoryAbstract<TEntity, TId> : IRepository<TEntity, TId> where TEntity : BaseEntity
    {
        protected ISession Session;

        public RepositoryAbstract(ISession session)
        {
            Session = session;
        }

        public TEntity FindById(TId id)
        {
            try
            {
                // 'Load' throws exception if object does not exist; 'Get' returns null
                return Session.Get<TEntity>(id);     
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error Getting entity of type " + typeof(TEntity).Name, exception);
            }
        
        }

        public TEntity LoadById(TId id)
        {
            try
            {
                // On the other hand, 'Load' does not hit the database if it does not need it, like when you need an Entity just for the foreign key association. 
                // But you have to KNOW that the entity exists, otherwise you get an exception.
                return Session.Load<TEntity>(id);
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error Getting entity of type " + typeof(TEntity).Name, exception);
            }
        }

        public IList<TEntity> FindAll()
        {
            try
            {
                return Session.CreateCriteria(typeof (TEntity)).List<TEntity>();
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error getting list of all entities of type " + typeof(TEntity).Name, exception);
            }
        }

       
        public void Save(TEntity entity)
        {
            try
            {
                Session.Save(entity);
                Session.Flush();
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error saving entity of type "+typeof(TEntity).Name, exception);
            }
        }

        public void SaveOrUpdate(TEntity entity)
        {
            try
            {
                Session.SaveOrUpdate(entity);
                Session.Flush();
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error saving/updating entity of type " + typeof(TEntity).Name, exception);
            }
        }

        public void BulkSave(IEnumerable<TEntity> list)
        {
            var transaction = Session.BeginTransaction();
            try
            {
                foreach (var entity in list)
                {
                    Session.Save(entity);
                }
                transaction.Commit();
            }
            catch (HibernateException exception)
            {
                transaction.Rollback();
                throw new PersistenceException("Error bulk saving entities of type " + typeof(TEntity).Name, exception);
            }
            
        }
        public void Update(TEntity entity)
        {
            try
            {
                Session.Update(entity);
                Session.Flush();
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error updating entity of type " + typeof(TEntity).Name, exception);
            }
        }

        public void Delete(TEntity entity)
        {
            try
            {
                Session.Delete(entity);
                Session.Flush();
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error deleting entity of type " + typeof(TEntity).Name, exception);
            }
        }

        public TEntity FindByEqRestrictionUniqueResult<T>(Expression<Func<TEntity, object>> expression, T value)
        {
            // get property name from the lambda expression:
            var property = ReflectionHelper.GetMember(expression).Name;
            try
            {
                
                var criteria = Session.CreateCriteria(typeof(TEntity));
                criteria.Add(Restrictions.Eq(property, value));
                return criteria.UniqueResult<TEntity>();
            }
            catch (HibernateException exception)
            {

                throw new PersistenceException("Error in FindByEqRestrictionUniqueResult, entity of type " + typeof(TEntity).Name, exception);
            }

        }

        public IList<TEntity> FindByEqRestrictionListResult<T>(Expression<Func<TEntity, object>> expression, T value)
        {
            // get property name from the lambda expression:
            var property = ReflectionHelper.GetMember(expression).Name;
            try
            {
                var criteria = Session.CreateCriteria(typeof(TEntity));
                criteria.Add(Restrictions.Eq(property, value));
                return criteria.List<TEntity>();
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error in FindByEqRestrictionListResult, entity of type " + typeof(TEntity).Name, exception);
            }
        }

        public IList<TEntity> FindByPreffix(Expression<Func<TEntity, object>> expression, string preffix)
        {
            var property = ReflectionHelper.GetMember(expression).Name;
            return FindLikeProperty(property, preffix + "%");

        }

        public IList<TEntity> FindByInffix(Expression<Func<TEntity, object>> expression, string inffix)
        {
            var property = ReflectionHelper.GetMember(expression).Name;
            return FindLikeProperty(property, "%" + inffix + "%");
        }

        public void ReopenSession()
        {
            if (!Session.IsConnected || !Session.IsOpen) Session = Session.SessionFactory.OpenSession();
        }

        private IList<TEntity> FindLikeProperty(string propertyName, string like)
        {
            try
            {
                var criteria = Session.CreateCriteria(typeof(TEntity));
                criteria.Add(Restrictions.Like(propertyName, like));
                return criteria.List<TEntity>();
            }
            catch (HibernateException exception)
            {
                throw new PersistenceException("Error in FindLikeProperty, entity of type " + typeof(TEntity).Name, exception);
            }
            
        }
    }
}